package slave;

import java.io.IOException;
import java.util.Vector;
import javax.media.Time;
import javax.media.control.BitRateControl;
import javax.media.rtp.Participant;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.RemoteListener;
import javax.media.rtp.SendStreamListener;
import javax.media.rtp.event.ByeEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.media.rtp.event.ReceiverReportEvent;
import javax.media.rtp.event.RemoteEvent;
import javax.media.rtp.event.SendStreamEvent;
import javax.media.rtp.rtcp.Feedback;
import javax.media.rtp.rtcp.Report;
import javax.swing.event.EventListenerList;
import com.intel.bluetooth.BlueCoveImpl;
import commonUtility.XmlHandler;
import slave.SlaveStreamProvider.ChangeCodingEvent;
import slave.SlaveStreamProvider.LambdaComputationEvent;
import slave.SlaveStreamProvider.TransferJobXmlReadyEvent;
import slave.sspGUI.MonitorObservervable;
import slave.sspGUI.StreamProviderMonitor;
import slave.RTPL2capAdapterSender.ConnectionDataErrorEvent;
import slave.RTPL2capAdapterSender.GetNewTimeValueEvent;

public class SessionStreamManager extends Thread implements RTPL2capAdapterSenderListener,SlaveStreamProviderListener,SendStreamListener,RemoteListener, ReceiveStreamListener{
	private StreamProviderMonitor systemMonitor;
	private XmlHandler xmlSynchronized;
	protected EventListenerList listenerList;
	private AudioBtTransmit audioBtTransmitter = null;
	private String locator;
	private String  btAddress;
	private SlaveStreamProvider provider; 
	private int secondsSend;
	private String starTime;
	private int coding;
	public String stackBT;
	private boolean delete = false;
	private MonitorObservervable obs;
	private Vector <String> message = new Vector<String>();
	private Vector<String> info = new Vector <String>();

	/**Questo thread attivato dallo SlaveStreamProvider avrà come primo compito
	 * attivare {@link AudioBtTransmit}, componente che permetterà
	 * l' invio di uno stream audio RTP, utilizzando il JMF.
	 * Una volta attivato sarà il gestore fino al termine di questo flusso di trasmissione,
	 * dovrà aggiornarne lo stato sul file gateway.xml e configurarlo a seconda delle
	 * richieste del sistema.
	 * @param systemMonitor : Riferimento al monitor(vista) del sistema.
	 * @param btClientReceiverAddress : Indirizzo MAC del dispositivo bluetooth 
	 * @param locator : Posizione del file audio da trasmettere.
	 * @param startTime : istante temporale per l'inizio della trasmissione.
	 * @param port : porta BT
	 * @param coding : cofica selezionata
	 * @param device : id del dispositivo da selezionare.
	 * @param provider : {@link SlaveStreamProvider}, per la gestione del file xml.
	 * @param stackBT : stabilisce quale device utilizzare.
	 * @author alex
	 */

	public SessionStreamManager(StreamProviderMonitor systemMonitor,String btClientReceiverAddress,String locator,String startTime,String port,int coding,SlaveStreamProvider provider, String stackBT){
		this.setName(btClientReceiverAddress);
		this.systemMonitor=systemMonitor;
		this.locator=locator;
		this.btAddress=btClientReceiverAddress;
		this.starTime=startTime;
		this.coding=coding;
		this.stackBT=stackBT;
		this.provider=provider;
		this.xmlSynchronized = provider.xmlSynchronized;
		listenerList = new EventListenerList();
		info.add(btAddress);
		obs = new MonitorObservervable(systemMonitor);

	}

	private void setBluetoothStack(){
		//Setto lo stack bluetooth.
		BlueCoveImpl.useThreadLocalBluetoothStack();	
		if(BluetoothStack.getInstance().getId(stackBT)==null){//Stack non ancora istanziato
			BluetoothStack.getInstance().chooseStackDevice(stackBT);		
		}else{//già istanziato
			BlueCoveImpl.setThreadBluetoothStackID(BluetoothStack.getInstance().getId(stackBT));	
		}
	}

	public void run(){
		setBluetoothStack();//Setto lo Stack passato
		audioBtTransmitter= new AudioBtTransmit(systemMonitor,locator, btAddress, this,coding,starTime);	
		try {
			System.out.println("Effettuo connesssione su 4097 per richiesta di Stream");
			String reply = audioBtTransmitter.getReceiverConfirmation();
			if(reply==null){//Trasferimento confermato
				System.out.println("Il client ha confermato la richiesta di invio stream");
				if(!audioBtTransmitter.isInitProcessor()){
				audioBtTransmitter.init();	
				}
				audioBtTransmitter.start();//Attivo i servizi di invio
				provider.addSlaveManagerListener(this);
			} else { //Richiesta di invio rifiutata, effettuo le operazioni di cancellazione
				xmlSynchronized.changeStatusClientXml("delete",info);//Rimosso da XML
				provider.removeFromActiveTable(btAddress);//Rimuovo Manager	
				audioBtTransmitter.stop(reply);
				if(!message.isEmpty()){
					message.clear();
				}
				message.add(0,"EVT");
				message.add(1,"SSM:"+btAddress+" has reply "+reply+" delete from xml");
				obs.notify(message);
			}
		} catch (IOException e) {
			provider.removeSlaveManagerListener(this);
			e.printStackTrace();
		}
	}

	/**
	 * Verifico lo stato del processor RTP
	 */

	@SuppressWarnings("static-access")
	public boolean getTrasmissionState() {
		if(audioBtTransmitter.processor.getState()==audioBtTransmitter.processor.Started){
			return true;
		}
		return false;
	}
	/**
	 * Rimuovo il client che sara' gestito da un altro trasmettitore.
	 * FAILOVER = cambio di trasmettitore.
	 */
	@SuppressWarnings("unchecked")
	public Vector removeClient(String reason){
		xmlSynchronized.changeStatusClientXml("delete",info);
		getBandWidht("delete");//GUI
		delete = true;
		Vector v = new Vector();
		if(!message.isEmpty()){
			message.clear();
		}
		message.add(0,"EVT");
		message.add(1,"SSM: Call REMOVE "+btAddress+" with "+reason+" reason.");
		obs.notify(message);
		try{
			double time = audioBtTransmitter.processor.getMediaTime().getSeconds();
			String port = audioBtTransmitter.btPort;
			audioBtTransmitter.stop(reason);
			provider.removeFromActiveTable(btAddress); //Rimuovo SSM da tabella in Memoria
			provider.removeSlaveManagerListener(this);
			String get_time = Double.toString(time);
			v.add(0,get_time);
			v.add(1,port);
			v.add(2,audioBtTransmitter.url);
			audioBtTransmitter.rtpl2capAdapterSender.removeRTPAdapterSenderListener(this);
			xmlSynchronized.changeStatusClientXml("delete",info);//Rimuovo Client da Xml
			if(!message.isEmpty()){
				message.clear();
			}
			message.add(0,"EVT");
			message.add(1,"SSM: "+btAddress+" delete from xml with "+reason+" reason.");
			obs.notify(message);
		}catch(Exception e){
			e.printStackTrace();		
		}
		return v;//Restituisco i valori richiesti dal master.
	}	

	/**
	 * @author Alex
	 * Show Reports/FeedBack for AudioBtTransmit class
	 */

	@SuppressWarnings("unchecked")
	public void l2capFeedbacks(){ 
		Participant participant; 
		Vector reports, feedbacks; 
		Report report; //Encapsulates a generic RTCP report; this is the parent interface for the SenderReport and ReceiverReport interfaces.
		Feedback feedback; //Contiene i valori dei campi del pacchetto RTCP
		Vector p = audioBtTransmitter.rtpMgrs.getPassiveParticipants();
		System.out.println("Numero partecipanti: "+ p.size());
		for(int i = 0; i< p.size(); i++){ 
			participant = (Participant)p.elementAt(i); 
			reports = participant.getReports();
			for( int j=0; j< reports.size(); j++){ 
				report = (Report) reports.elementAt(j); 
				feedbacks = report.getFeedbackReports(); 
				for( int k=0; k < feedbacks.size(); k++){ 
					feedback = (Feedback) feedbacks.elementAt(k); 
					/*Stampo il contenuto del pacchetto RTCP*/
					System.out.println("getSSRC: "+feedback.getSSRC());
					System.out.println("getFractionLost: "+String.valueOf(feedback.getFractionLost()));
					System.out.println("getJitter: "+String.valueOf(feedback.getJitter()));
					System.out.println("getLSR: "+String.valueOf(feedback.getLSR()));
					System.out.println("getNumLost: "+String.valueOf(feedback.getNumLost()));
					System.out.println("getDLSR: "+String.valueOf(feedback.getDLSR()));
					System.out.println("[SessionStreamManager]RTCP packet received #getXtndSeqNum#: "+String.valueOf(feedback.getXtndSeqNum()));
				} 
			} 
		} 
	}// l2capFeedbacks

	//Setto il nuovo RSSI ad ogni pacchetto RTCP
	private void setRssi(){
		String rssi = getRssi();
		audioBtTransmitter.setSourceDescription(rssi);
		audioBtTransmitter.sendStream.setSourceDescription(audioBtTransmitter.userdesclist);
	}


	//Restituisce il valore dell' RSSI 
	public String getRssi(){
		Vector<String> v = xmlSynchronized.changeStatusClientXml("rssi",info); 
		String rssi = v.elementAt(0);
		return rssi;	   
	}
	/**
	 * Restituisce il valore di file trasmesso fino all'istante della richiesta.
	 * @return
	 * @throws IOException
	 */

	public double getMediaTime() throws IOException{
		return audioBtTransmitter.processor.getMediaTime().getSeconds();
	}

	public void ctrlFileDuration(){
		try {   
			System.out.println("DEBUG: JVM bytes liberi in Memoria: "+Runtime.getRuntime().freeMemory());
			System.out.println("Secondi trasmessi:"+audioBtTransmitter.processor.getMediaTime().getSeconds());
			BitRateControl p = (BitRateControl) audioBtTransmitter.processor.getControl("javax.media.control.BitRateControl");
			System.out.println("[SessionStreamManager] "+btAddress+" CODING bitrate: "+ p.getBitRate()/8000);
			secondsSend =  (int) audioBtTransmitter.processor.getMediaTime().getSeconds();
			if(secondsSend==audioBtTransmitter.seconds){
				getBandWidht("delete");
				System.err.println("[SessionStreamManager]File transmission completed.., stop the System.");
				audioBtTransmitter.stop("COMPLETED");
				xmlSynchronized.changeStatusClientXml("delete",info);	//Rimosso da XML
				provider.removeFromActiveTable(btAddress); //Rimosso da tabella in Memoria
				provider.removeSlaveManagerListener(this);
				audioBtTransmitter.rtpl2capAdapterSender.removeRTPAdapterSenderListener(this);
			}
			getBandWidht("update");			

		} catch (Exception e) {
			System.out.println("[SessionStreamManager] Eccezione in ctrlFileDuration: "+ e);
		}
	}

	/**
	 * Metodo utilizzato per notificare ai Monitor l'aggiornamento della velocità di
	 * Trasmissione dello Stream RTP.
	 */

	private void getBandWidht(String msg) {
		try{
			if(audioBtTransmitter.processor!=null && !delete){
				Vector<String> message = new Vector<String>();
				message.add(msg);//update viste o rimozione del client
				message.add(btAddress);//mac del client
				message.add(audioBtTransmitter.btPort);
				message.add(BluetoothStack.getInstance().getId(stackBT).toString());
				message.add(audioBtTransmitter.name);
				BitRateControl p = (BitRateControl) audioBtTransmitter.processor.getControl("javax.media.control.BitRateControl");			
				message.add(Double.toString(p.getBitRate()/8000.00));//KBps Trasmessi
				message.add(Integer.toString(audioBtTransmitter.seconds));//Lunghezza del file
				//	System.out.println("Secondi trasmessi: "+(int)processor.getMediaTime().getSeconds()+"Stato del JMF Processor: "+	processor.getState());
				if(!audioBtTransmitter.end){
					System.out.println("Secondi Trasmessi dentro if");
					message.add(Integer.toString((int)audioBtTransmitter.processor.getMediaTime().getSeconds()));//Secondi Trasmessi
					if(audioBtTransmitter.seconds==(int)audioBtTransmitter.processor.getMediaTime().getSeconds()){
						audioBtTransmitter.end = true;	
					}
				} else 
				{
					message.add(Integer.toString(audioBtTransmitter.seconds));//Secondi Trasmessi
				}
				obs.notify(message);
			}
		}
		catch (Exception e){
			e.printStackTrace();	
		}  	
	}

	/*******************************************************************************
	 * ASCOLTATORI DEGLI EVENTI DELLO STREAM RTP
	 *******************************************************************************/

	/**
	 * AGGIORNO IL FILE XML ad ogni pacchetto RTCP ricevuto, e controllo se e' terminato il file.
	 */

	public void update(RemoteEvent remoteEvent) {
		if (remoteEvent instanceof ReceiverReportEvent){
			try{
				if(!info.isEmpty()){
					info.clear();
				}
				info.add(0,btAddress);   
				info.add(1,audioBtTransmitter.url);
				info.add(2,Integer.toString((int)audioBtTransmitter.processor.getMediaTime().getSeconds()));
				info.add(3,null);
				info.add(4,audioBtTransmitter.btPort);
				info.add(5,"ACTIVE");
				info.add(6,null);
				xmlSynchronized.changeStatusClientXml("update",info);
				//	l2capFeedbacks();		
				ctrlFileDuration();
				setRssi();

			}catch(Exception e ){				
			}
		}  					
	}

	public void update(ReceiveStreamEvent receiveStreamEvent) {
		if (receiveStreamEvent instanceof ByeEvent){
			String bye= ((ByeEvent) receiveStreamEvent).getReason();
			if(!message.isEmpty()){
				message.clear();
			}
			message.add(0,"EVT");
			message.add(1,"SSM: Received bye event "+bye+" from "+btAddress);
			obs.notify(message);
			if(bye.contains("DISACTIVE")){
				getBandWidht("delete");	
				delete=true;
				audioBtTransmitter.stop("DISACTIVE");
				audioBtTransmitter.rtpl2capAdapterSender.close();
				xmlSynchronized.changeStatusClientXml("delete",info);//Rimosso da XML
				provider.removeFromActiveTable(btAddress); //Rimosso da tabella in Memoria
				provider.removeSlaveManagerListener(this);
				audioBtTransmitter.rtpl2capAdapterSender.removeRTPAdapterSenderListener(this);
				System.out.println("Rimosso da tabella: "+btAddress);
				BluetoothClientPortContainer.getInstance().removePort(audioBtTransmitter.btPort);		
			}
			System.err.println("[SessionStreamManager]Ricevuto Bye... "+bye);	
		}		
	}

	/*******************************************************************************
	 * ASCOLTATORI DEGLI EVENTI DEL RTPL2CAPADAPTER SENDER
	 *******************************************************************************/
	/**
	 * Ricevo un evento generato per la chiusura della connessione Bluetooth,
	 * possibile causa hardware->distanza del dispositivo.
	 */
	public void connectionDataError(ConnectionDataErrorEvent evt) {
		System.err.println("[SessionStreamManager]Bluetooth Connection Closed..,trasmission not completed!!");
		xmlSynchronized.changeStatusClientXml("delete",info);
		getBandWidht("delete");
		if(!message.isEmpty()){
			message.clear();
		}
		message.add(0,"EVT");
		message.add(1,"SSM: "+btAddress+" delete from xml");
		obs.notify(message);
		audioBtTransmitter.stop("ERROR");
		audioBtTransmitter.rtpl2capAdapterSender.removeRTPAdapterSenderListener(this);
		provider.removeSlaveManagerListener(this);
		provider.removeFromActiveTable(btAddress); //Rimosso da tabella in Memoria
		BluetoothClientPortContainer.getInstance().removePort(audioBtTransmitter.btPort);
	}
	/**
	 * Questo evento e' generato se il client avanza il cursore della riproduzione.
	 */
	public void getNewTimeValue(GetNewTimeValueEvent evt) {
		if(evt.getTime()!=""){	
			System.out.println("[SessionStreamManager]Chiamato processor.setMediaTime: " +evt.getTime() )	;
			double time = Double.parseDouble(evt.getTime());
			audioBtTransmitter.processor.setMediaTime(new Time(time));
			if(!message.isEmpty()){
				message.clear();
			}
			message.add(0,"EVT");
			message.add(1,"SSM:"+btAddress+" Set new media time "+time);
			obs.notify(message);
		}
	}

	/*******************************************************************************
	 * ASCOLTATORI DEGLI EVENTI DEL PROVIDER
	 *******************************************************************************/
	/**
	 * Questo evento e' generato dal Provider se necessario un cambio di decodifica e del
	 * relativo utilizzo di banda.
	 */
	public void changeCoding(ChangeCodingEvent evt) {
		System.out.println("Ricevuto cambio di coding");
		String time = String.valueOf(audioBtTransmitter.processor.getMediaTime().getSeconds());	
		System.out.println("Ricevuto cambio di coding"+time);
		audioBtTransmitter.stop("CHANGECODING");	
		int coding =evt.getCoding();
		audioBtTransmitter.coding = coding;
		audioBtTransmitter.startTime = time;
		audioBtTransmitter.init();
		audioBtTransmitter.start();//Attivazione
		if(!message.isEmpty()){
			message.clear();
		}
		message.add(0,"EVT");
		message.add(1,"SSM:"+btAddress+" Received change coding event: "+coding);
		obs.notify(message);
	}

	@Override
	public void lambdaComputationDone(LambdaComputationEvent evt) {		
	}

	@Override
	public void transferJobXmlReady(
			TransferJobXmlReadyEvent evt,
			int id,
			SlaveStreamProvider slaveManager) {		
	}

	@Override
	public void update(SendStreamEvent arg0) {

	} 
}